﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceProcess;
using System.Xml;
using System.Threading;
using System.Net;
using System.ComponentModel;
using log4net;
using System.IO;
using MonoTorrent.Client;
using Newtonsoft.Json;
using MonoTorrent.Common;
using System.Diagnostics;
using MonoTorrent.Client.Tracker;
using HtmlAgilityPack;
using System.Xml.Xsl;
using System.Collections.Specialized;
using XmlSettings;
namespace BotTorrent
{
    internal class WebUI : ServiceBase
{
    // Fields
    private IContainer components = null;
    private XmlDocument indexDocument;
    private const string indexFile = "index.html";
    private XmlNode indexTokenNode;
    private HttpListener listener;
    private Thread listenWorker;
    private static readonly ILog log = LogManager.GetLogger(typeof(BotTorrent));
    private object requestProcessLock = new object();
    private BitTorrentClient service;
    private object serviceControlLock = new object();
    public Settings Settings;
    private bool stopFlag = false;
    private const string tokenFile = "token.html";
    private const string urlBase = "/gui/";
    private const string urlBaseNoSlash = "/gui";
    private const string urlBaseNoSlashQuery = "/gui?";
    private const string urlBaseWithQuery = "/gui/?";

    // Methods
    public WebUI(BitTorrentClient monoTorrentService)
    {
        if (monoTorrentService == null)
        {
            throw new ArgumentNullException("monoTorrentService");
        }
        this.InitializeComponent();
        this.listener = new HttpListener();
        this.service = monoTorrentService;
        this.listenWorker = new Thread(new ThreadStart(this.ListenLoop));
    }

    private void AddFile(Stream fileData)
    {
    }

    private void ApplyConfiguration()
    {
        this.listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
        this.listener.Prefixes.Clear();
        this.listener.Prefixes.Add("http://*:7888/gui/");
    }

    private WebOption BooleanAdapter(bool? value)
    {
        if (value.HasValue)
        {
            return (value.Value ? WebOption.Enabled : WebOption.Disabled);
        }
        return WebOption.NotAllowed;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing && (this.components != null))
        {
            this.components.Dispose();
        }
        base.Dispose(disposing);
    }

    private void GetFeed(Stream OutputStream, string feedurl)
    {
        try
        {
            feedurl = Uri.UnescapeDataString(feedurl);
            string responseXml = new WebClient().DownloadString(feedurl);
            byte[] data = Encoding.UTF8.GetBytes(responseXml);
            OutputStream.Write(data, 0, data.Length);
        }
        catch (Exception)
        {
        }
    }

    private string GetTrackerString(TorrentManager torrent)
    {
        StringBuilder value = new StringBuilder();
        foreach (TrackerTier tier in torrent.TrackerManager.TrackerTiers)
        {
            foreach (MonoTorrent.Client.Tracker.Tracker tracker in tier)
            {
                value.Append(tracker.Uri);
                value.Append("\r\n");
            }
            value.Append("\r\n");
        }
        return value.ToString();
    }

    private void InitializeComponent()
    {
        base.CanPauseAndContinue = true;
        base.ServiceName = "MonoTorrent Web UI";
    }

    private void JsonGetSettings(HttpListenerResponse resp)
    {
        resp.ContentType = "application/json";
        resp.ContentEncoding = Encoding.ASCII;
        JsonSerializer serialiser = new JsonSerializer();
        using (JsonWriter writer = new JsonWriter(new StreamWriter(resp.OutputStream, resp.ContentEncoding)))
        {
            writer.Formatting = Newtonsoft.Json.Formatting.Indented;
            serialiser.Serialize(writer, this.service.Settings);
        }
    }

    private void JsonListTorrents(HttpListenerResponse Response, Stream outputstream)
    {
        Response.ContentType = "application/json";
        Response.ContentEncoding = Encoding.ASCII;
        using (JsonWriter writer = new JsonWriter(new StreamWriter(outputstream, Response.ContentEncoding)))
        {
            writer.Formatting = Newtonsoft.Json.Formatting.Indented;
            writer.WriteStartObject();
            writer.WritePropertyName("torrents");
            writer.WriteStartArray();
            foreach (TorrentManager torrent in this.service.TorrentList)
            {
                string hash = Toolbox.ToHex(torrent.Torrent.InfoHash);
                long remBytes = (long) Math.Round((double) (torrent.Torrent.Size * (1.0 - torrent.Progress)));
                long remSeconds = (long) Math.Round((double) (((double) remBytes) / ((double) torrent.Monitor.DownloadSpeed)));
                int ratio = (int) Math.Round((double) ((torrent.Monitor.DataBytesDownloaded * 10.0) / ((double) torrent.Monitor.DataBytesUploaded)));
                int totalPeers = torrent.Peers.Leechs + torrent.Peers.Seeds;
                int progress = (int) Math.Round((double) (torrent.Progress * 10.0));
                writer.WriteStartObject();
                writer.WritePropertyName("hash");
                writer.WriteValue(hash);
                writer.WritePropertyName("State");
                switch (torrent.State) {
                    case TorrentState.Downloading:
                        writer.WriteValue("Downloading");
                        break;
                    
            }
                writer.WritePropertyName("Name");
                writer.WriteValue(torrent.Torrent.Name);
                writer.WritePropertyName("Size");
                writer.WriteValue(torrent.Torrent.Size);
                writer.WritePropertyName("progress");
                writer.WriteValue(progress);
                writer.WritePropertyName("DataBytesDownloaded");
                writer.WriteValue(torrent.Monitor.DataBytesDownloaded);
                writer.WritePropertyName("DataBytesUploaded");
                writer.WriteValue(torrent.Monitor.DataBytesUploaded);
                writer.WritePropertyName("ratio");
                writer.WriteValue(ratio);
                writer.WritePropertyName("UploadSpeed");
                writer.WriteValue(torrent.Monitor.UploadSpeed);
                writer.WritePropertyName("DownloadSpeed");
                writer.WriteValue(torrent.Monitor.DownloadSpeed);
                writer.WritePropertyName("remSeconds");
                writer.WriteValue(remSeconds);
                writer.WritePropertyName("Label");
                writer.WriteValue("");
                writer.WritePropertyName("totalPeers");
                writer.WriteValue(totalPeers);
                writer.WritePropertyName("totalPeersCon");
                writer.WriteValue(totalPeers);
                writer.WritePropertyName("Seeds");
                writer.WriteValue(torrent.Peers.Seeds);
                writer.WritePropertyName("SeedsCon");
                writer.WriteValue(totalPeers);
                writer.WritePropertyName("Availability");
                writer.WriteValue(1);
                writer.WritePropertyName("remBytes");
                writer.WriteValue(remBytes);
                writer.WriteEndObject();
            }
            writer.WriteEndArray();
            writer.WriteEndObject();
        }
    }

    private void JsonSetSettings(HttpListenerResponse httpListenerResponse)
    {
        throw new NotImplementedException();
    }

    private void ListenLoop()
    {
        while (!this.stopFlag)
        {
            HttpListenerContext context = null;
            try
            {
                try
                {
                    context = this.listener.GetContext();
                    lock (this.requestProcessLock)
                    {
                        log.Debug("Received request: " + context.Request.RawUrl);
                        this.MarshalRequest(context);
                    }
                }
                catch (ObjectDisposedException)
                {
                }
                catch (Exception ex)
                {
                    Trace.WriteLine("Error: " + ex.Message);
                }
            }
            finally
            {
                if (context != null)
                {
                    context.Response.Close();
                }
            }
        }
    }

    

    private void MarshalRequest(HttpListenerContext context)
    {
        if (context.Request.HttpMethod.ToUpper() == "POST")
        {
            if (context.Request.RawUrl == "/gui/command.cgi")
            {
                this.ProcessPost(context);
            }
        }
        else if (context.Request.HttpMethod.ToUpper() == "GET")
        {
            if (!context.Request.RawUrl.StartsWith("/gui/"))
            {
                if (context.Request.RawUrl.StartsWith("/gui?") || context.Request.RawUrl.StartsWith("/gui"))
                {
                    string withMissingSlash;
                    int queryStart = context.Request.RawUrl.IndexOf('?');
                    if (queryStart > -1)
                    {
                        withMissingSlash = context.Request.RawUrl.Insert(queryStart, "/");
                    }
                    else
                    {
                        withMissingSlash = context.Request.RawUrl + "/";
                    }
                    if (withMissingSlash.StartsWith("/gui/"))
                    {
                        context.Response.StatusCode = 0x12d;
                        context.Response.Redirect(withMissingSlash);
                    }
                    else
                    {
                        this.ProcessInvalidRequest(context);
                    }
                }
                else
                {
                    this.ProcessInvalidRequest(context);
                }
            }
            
            else
            {
        this.ProcessFile(context);
            }
        }
    }

    protected override void OnContinue()
    {
        lock (this.serviceControlLock)
        {
            Monitor.Exit(this.requestProcessLock);
        }
    }

    protected override void OnPause()
    {
        lock (this.serviceControlLock)
        {
            Monitor.Enter(this.requestProcessLock);
        }
    }

    protected override void OnStart(string[] args)
    {
        lock (this.serviceControlLock)
        {
            this.ApplyConfiguration();
            this.stopFlag = false;
            
            this.listener.Start();
            this.listenWorker.Start();
        }
    }

    protected override void OnStop()
    {
        lock (this.serviceControlLock)
        {
            this.stopFlag = true;
            this.listener.Stop();
            if (!this.listenWorker.Join(0x1388))
            {
                this.listener.Abort();
                this.listenWorker.Join();
            }
        }
    }

    private void PauseTorrents(HttpListenerResponse resp, string hashes)
    {
        string[] hashList = hashes.Split(new char[] { ',' });
        foreach (string hash in hashList)
        {
            this.service.PauseTorrent(hash);
        }
    }

    private void PrintFiles(JsonWriter writer, string hash)
    {
        TorrentManager details = this.service.GetTorrentManager(hash);
        writer.WritePropertyName("files");
        writer.WriteStartArray();
        writer.WriteValue(hash);
        if (details != null)
        {
            writer.WriteStartArray();
            foreach (TorrentFile file in details.FileManager.Files)
            {
                writer.WriteStartArray();
                writer.WriteValue(file.Path);
                writer.WriteValue(file.Length);
                writer.WriteValue(0);
                writer.WriteValue((int) this.PriorityAdapter(file.Priority));
                writer.WriteEndArray();
            }
            writer.WriteEndArray();
        }
        else
        {
            writer.WriteNull();
        }
        writer.WriteEndArray();
    }

    private void PrintLabels(JsonWriter writer)
    {
        Dictionary<string, int> labels = new Dictionary<string, int>();
        writer.WritePropertyName("label");
        writer.WriteStartArray();
        foreach (KeyValuePair<string, int> label in labels)
        {
            writer.WriteStartArray();
            writer.WriteValue(label.Key);
            writer.WriteValue(label.Value);
            writer.WriteEndArray();
        }
        writer.WriteEndArray();
    }

    private void PrintProperties(JsonWriter writer, string hash)
    {
        TorrentManager details = this.service.GetTorrentManager(hash);
        writer.WritePropertyName("props");
        writer.WriteStartArray();
        if (details != null)
        {
            writer.WriteStartObject();
            int dhtStatus = details.CanUseDht ? ((int) this.BooleanAdapter(new bool?(details.Settings.UseDht))) : -1;
            writer.WritePropertyName("hash");
            writer.WriteValue(hash);
            writer.WritePropertyName("trackers");
            writer.WriteValue(this.GetTrackerString(details));
            writer.WritePropertyName("ulrate");
            writer.WriteValue(details.Settings.MaxUploadSpeed);
            writer.WritePropertyName("dlrate");
            writer.WriteValue(details.Settings.MaxDownloadSpeed);
            writer.WritePropertyName("superseed");
            writer.WriteValue((int) this.BooleanAdapter(new bool?(details.Settings.InitialSeedingEnabled)));
            writer.WritePropertyName("dht");
            writer.WriteValue(dhtStatus);
            writer.WritePropertyName("pex");
            writer.WriteValue(0);
            writer.WritePropertyName("seed_override");
            writer.WriteValue(0);
            writer.WritePropertyName("seed_ratio");
            writer.WriteValue(0);
            writer.WritePropertyName("seed_time");
            writer.WriteValue(0);
            writer.WritePropertyName("ulslots");
            writer.WriteValue(details.Settings.UploadSlots);
            writer.WriteEndObject();
        }
        else
        {
            writer.WriteNull();
        }
        writer.WriteEndArray();
    }

    private void PrintSettings(JsonWriter writer, string settingsFile)
    {
        writer.WritePropertyName("settings");
        writer.WriteStartArray();
        using (StreamReader sr = new StreamReader(settingsFile))
        {
            string line;
            while ((line = sr.ReadLine()) != null)
            {
                string[] parts = line.Split(", ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                writer.WriteStartArray();
                foreach (string part in parts)
                {
                    if (part.StartsWith("\"") && part.EndsWith("\""))
                    {
                        writer.WriteValue(part.Substring(1, part.Length - 2));
                    }
                    else
                    {
                        int numValue;
                        if (int.TryParse(part, out numValue))
                        {
                            writer.WriteValue(numValue);
                        }
                    }
                }
                writer.WriteEndArray();
            }
        }
        writer.WriteEndArray();
    }

   

    private Priority PriorityAdapter(WebPriority priority)
    {
        if (priority == WebPriority.Skip)
        {
            return Priority.DoNotDownload;
        }
        if (priority == WebPriority.Low)
        {
            return Priority.Low;
        }
        if (priority == WebPriority.High)
        {
            return Priority.High;
        }
        return Priority.Normal;
    }

    private WebPriority PriorityAdapter(Priority priority)
    {
        if (priority == Priority.DoNotDownload)
        {
            return WebPriority.Skip;
        }
        if ((priority == Priority.Low) || (priority == Priority.Lowest))
        {
            return WebPriority.Low;
        }
        if (((priority == Priority.High) || (priority == Priority.Highest)) || (priority == Priority.Immediate))
        {
            return WebPriority.High;
        }
        return WebPriority.Normal;
    }

    private void ProcessFile(HttpListenerContext context)
    {
        string filePath = context.Request.RawUrl.Substring("/gui/".Length);
        string query = null;
        int queryStart = filePath.IndexOf('?');
        if (queryStart > -1)
        {
            query = filePath.Substring(queryStart);
            filePath = filePath.Substring(0, filePath.Length - queryStart);
        }
        if (string.IsNullOrEmpty(filePath))
        {
            filePath = "index.html";
        }
        filePath = Path.Combine(Path.GetFullPath(@".\WebUI\"), filePath);
        string fileName = Path.GetFileName(filePath);
    
       
        if (File.Exists(filePath))
        {
            using (FileStream data = File.OpenRead(filePath))
            {
                int count;
                byte[] buffer = new byte[0x400];
                while ((count = data.Read(buffer, 0, buffer.Length)) > 0)
                {
                    context.Response.OutputStream.Write(buffer, 0, count);
                }
            }
        }
        else
        {
            this.Respond(context, HttpStatusCode.NotFound, "404 File not found.");
        }
    }

    private void ProcessIndexFileRequest(HttpListenerContext context, string filePath)
    {
        lock (this.indexDocument)
        {
            this.indexTokenNode.InnerText = Guid.NewGuid().ToString();
            context.Response.ContentType = "text/html";
            using (XmlWriter writer = new XmlTextWriter(context.Response.OutputStream, Encoding.UTF8))
            {
                this.indexDocument.WriteTo(writer);
            }
        }
    }

    private void ProcessInvalidRequest(HttpListenerContext context)
    {
        this.Respond(context, HttpStatusCode.BadRequest, "invalid request");
    }

    private void ProcessPost(HttpListenerContext context)
    {
        string encodedString = new StreamReader(context.Request.InputStream).ReadToEnd();
        NameValueCollection qs = new NameValueCollection();
        string[] keys = encodedString.Split("&".ToCharArray());
        foreach (string key in keys)
        {
            string[] part = key.Split("=".ToCharArray());
            if (part.Length == 1)
            {
                qs.Add(part[0], "");
            }
            qs.Add(part[0], part[1]);
        }
        switch (qs["action"])
        {
            case "listTorrents":
                this.JsonListTorrents(context.Response, context.Response.OutputStream);
                break;

            case "searchPB":
                this.SearchPirateBay(context.Response.OutputStream, qs["query"]);
                break;

            case "getFeed":
                this.GetFeed(context.Response.OutputStream, qs["feed"]);
                break;

            case "getSettings":
                this.JsonGetSettings(context.Response);
                break;

            case "setSettings":
                this.JsonSetSettings(context.Response);
                break;

            case "stopTorrent":
                this.StopTorrents(context.Response, qs["hashes"]);
                break;

            case "startTorrent":
                this.StartTorrents(context.Response, qs["hashes"]);
                break;

            case "pauseTorrent":
                this.PauseTorrents(context.Response, qs["hashes"]);
                break;

            case "removeTorrents":
                this.RemoveTorrents(context.Response, qs["hashes"], qs["removedata"]);
                break;

            case "addurl":
            {
                string url = qs["url"];
                byte[] data = new WebClient().DownloadData(url);
                this.AddFile(new MemoryStream(data, 0, data.Length, false, false));
                break;
            }
        }
    }

  

   

    private void RemoveTorrents(HttpListenerResponse resp, string hashes, string removeData)
    {
        string[] splitHashes = hashes.Split(new char[] { ',' });
        JsonWriter writer = new JsonWriter(new StreamWriter(resp.OutputStream));
        writer.WritePropertyName("torrentm");
        writer.WriteStartArray();
        foreach (string hash in splitHashes)
        {
            writer.WriteValue(hash);
        }
        writer.WriteEndArray();
    }

    private void Respond(HttpListenerContext context, HttpStatusCode httpStatusCode, string message)
    {
        context.Response.StatusCode = (int) httpStatusCode;
        byte[] data = Encoding.UTF8.GetBytes(message);
        context.Response.ContentEncoding = Encoding.UTF8;
        context.Response.OutputStream.Write(data, 0, data.Length);
    }

    private void SearchPirateBay(Stream outputStream, string searchTerm)
    {
        string html = new WebClient().DownloadString(string.Format("http://thepiratebay.org/s/?q={0}&searchTitle=on&page=0&orderby=99", searchTerm));
        string xsltpath = "piratebay.xslt";
        XmlTextWriter writer = new XmlTextWriter(outputStream, Encoding.UTF8);
        HtmlDocument doc2 = new HtmlDocument();
        doc2.LoadHtml(html);
        XsltArgumentList xsltArgs = new XsltArgumentList();
        xsltArgs.AddParam("url", "", "SOMESEARCHURL");
        xsltArgs.AddParam("requestDuration", "", "50");
        xsltArgs.AddParam("fromCache", "", "YESFROMCACHE");
        XslCompiledTransform xslt = new XslCompiledTransform();
        xslt.Load(xsltpath);
        xslt.Transform(doc2, xsltArgs, outputStream);
    }

    private void SetFilePriority(string hash, string fileIndexes, int webPriority)
    {
        Priority priority = this.PriorityAdapter((WebPriority) webPriority);
        string[] splitIndexes = fileIndexes.Split(new char[] { ',' });
        List<int> parsedIndexes = new List<int>(splitIndexes.Length);
        foreach (string fileIndex in splitIndexes)
        {
            try
            {
                parsedIndexes.Add(int.Parse(fileIndex));
            }
            catch
            {
            }
        }
    }

    public void SetLabel(string hash, string label)
    {
    }

    [Conditional("DEBUG")]
    public void StartService()
    {
        this.OnStart(new string[0]);
    }

    private void StartTorrents(HttpListenerResponse resp, string hashes)
    {
        string[] hashList = hashes.Split(new char[] { ',' });
        foreach (string hash in hashList)
        {
            this.service.StartTorrent(hash);
        }
    }

    private WebState StateAdapter(TorrentState state)
    {
        if (state == TorrentState.Paused)
        {
            return WebState.Paused;
        }
        if (state == TorrentState.Hashing)
        {
            return WebState.Queued;
        }
        if ((state == TorrentState.Downloading) || (state == TorrentState.Seeding))
        {
            return WebState.Active;
        }
        return WebState.Stopped;
    }

    [Conditional("DEBUG")]
    public void StopService()
    {
        this.OnStop();
    }

    private void StopTorrents(HttpListenerResponse resp, string hashes)
    {
        string[] hashList = hashes.Split(new char[] { ',' });
        foreach (string hash in hashList)
        {
            this.service.StopTorrent(hash);
        }
    }

    // Nested Types
    private enum WebOption
    {
        Disabled = 0,
        Enabled = 1,
        NotAllowed = -1
    }

    private enum WebPriority
    {
        Skip,
        Low,
        Normal,
        High
    }

    private enum WebState
    {
        Active = 0xc9,
        Paused = 0xe9,
        Queued = 200,
        Stopped = 0x88
    }
}

 

 

}
